#ifndef __DDLOG_H__
#define __DDLOG_H__

#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>

/*
 * *Note:* all functions in this library, with the exception of
 * `ddlog_record_commands()` and `ddlog_stop()`, and `ddlog_delta_XXX`
 * are thread-safe. E.g., it is legal to call `ddlog_transaction_start()`
 * from thread 1, `ddlog_apply_ovsdb_updates()` from thread 2, and
 * `ddlog_transaction_commit()` from thread 3.  Multiple concurrent
 * updates from different threads are also valid.
 *
 * However, DDlog currently does not support concurrent or nested
 * transactions.  An attempt to start a transaction while another
 * transaction is in progress (in the same or different thread) will
 * return an error.
 */

/*
 * Opaque handle to an instance of DDlog program.
 */
typedef void * ddlog_prog;

/*
 * An object that represents a set of changes to DDlog relations.  Internally,
 * it is a map from table id to a set of modified records in this table.  The
 * latter is, in turn, a map from record to integer weight associated with the
 * record. `weight > 0` indicates that the record has been added to the
 * relation; `weight < 0` indicates that the record has been removed from the
 * relation.  At present, valid values for weight are `1` and `-1`.
 *
 * Deltas are generated by the `ddlog_transaction_commit_dump_changes()` API
 * and are manipulated by `ddlog_delta_XXX` functions:
 *
 * - `ddlog_new_delta()` - create an empty delta.
 * - `ddlog_delta_get_table()` - retrieve changes to a specified table.
 * - `ddlog_delta_enumerate()` - enumerate changes in this delta.
 * - `ddlog_delta_clear_table()` - clear changes to a specified table.
 * - `ddlog_delta_union()` - aggregate changes from two deltas.
 * - `ddlog_delta_clear()` - clear delta.
 * - `ddlog_free_delta()` - deallocate delta.
 *
 * Use case 1: Enumerate changes output by a transaction.
 *   ```
 *   delta = ddlog_transaction_commit_dump_changes();
 *   ddlog_delta_enumerate(delta, ...);
 *   ddlog_free_delta(delta);
 *   ```
 *
 * Use case 2: Track complete state of an output table.
 *   ```
 *   // Initialization:
 *   delta = ddlog_new_delta();
 *   ...
 *   // Transaction commit:
 *   new_delta = ddlog_transaction_commit_dump_changes();
 *   ddlog_delta_union(delta, new_delta);
 *   ```
 *
 * Use case 3: Accumulate changes to a table over multiple transactions.
 *   ```
 *   // Initialization:
 *   delta = ddlog_new_delta();
 *   ...
 *   // Transaction commit:
 *   new_delta = ddlog_transaction_commit_dump_changes();
 *   ddlog_delta_union(delta, ddlog_delta_get_table(new_delta, table_id));
 *   ...
 *   // Reset delta:
 *   ddlog_delta_clear(delta);
 *   ```
 */
typedef struct ddlog_delta_struct * ddlog_delta;

/*
 * Insert or delete command to be passed to the `ddlog_apply_updates()`
 */
typedef void ddlog_cmd;

/*
 * Unique DDlog table identifier
 */
typedef size_t table_id;

/*
 * Unique DDlog index identifier
 */
typedef size_t index_id;

/*
 * The record type represents DDlog values that can be written to and
 * read from the DDlog database.
 *
 * DDlog supports the following value types:
 * - booleans
 * - arbitrary-width integers (but the API currently only supports
 *   integers up to 64 bits)
 * - strings
 * - tuples
 * - vectors
 * - sets
 * - maps
 * - variant types (aka structs)
 */
typedef void ddlog_record;

typedef struct {
    table_id table;
    ddlog_record *rec;
    ssize_t weight;
} ddlog_record_update;

/* DDlog profiling modes. */
typedef enum {
    // Profiling disabled.
    ddlog_disable_profiling = 0,
    // Enable DDlog self-profiler.
    ddlog_self_profiling    = 1,
    // Send profiling events to an external profiler.
    ddlog_timely_profiling  = 2
} ddlog_profiling_mode;

/* Timely/differential logging mode. */
typedef enum {
    // Disable log stream.
    ddlog_log_disabled  = 0,
    // Send profiling events to socket.
    ddlog_log_to_socket = 1,
    // Write profiling events to disk.
    ddlog_log_to_disk   = 2
} ddlog_log_mode;

/* Location to send a timely or differential profiling event stream. */
typedef struct {
    // Logging mode.
    ddlog_log_mode mode;
    // NULL, if mode == ddlog_log_disabled,
    // Socket address, e.g., "127.0.0.1:51317", if mode == ddlog_log_to_socket,
    // Directory path, e.g., "./timely_trace", if mode == ddlog_log_to_disk.
    const char *address_str;
} ddlog_log_destination;

/* Profiling configuration. */
typedef struct {
    ddlog_profiling_mode mode;

    /* The following fields are only used if (mode == ddlog_self_profiling) */

    // Directory to store self-profiler output.  Set to NULL to use current
    // working directory.
    const char *self_profiler_dir;

    /* The following fields are only used if (mode == ddlog_timely_profiling) */

    // Destination for the timely log stream.
    ddlog_log_destination timely_destination;
    // Destination for timely progress logging.
    ddlog_log_destination timely_progress_destination;
    // Destination for Differential Dataflow events.
    ddlog_log_destination differential_destination;
} ddlog_profiling_config;

/* The configuration for a DDlog program. */
typedef struct {
    // The number of timely worker threads.
    // If set to 0, the default number of threads is created (currently, 1).
    unsigned int num_timely_workers;
    // Whether extra regions should be added to the dataflow
    //
    // These extra regions *significantly* help with the readability
    // of the generated dataflows at the cost of a minor performance
    // penalty.
    bool enable_debug_regions;
    // Profiling configuration.
    ddlog_profiling_config profiling_config;
    // An amount of arrangement effort to spend each scheduling quantum
    //
    // See [`differential_dataflow::Config`]
    ssize_t differential_idle_merge_effort;
} ddlog_config;

/*
 * Default DDlog configuration.
 */
extern ddlog_config ddlog_default_config(void);

/*
 * Create an instance of DDlog program.
 *
 * This function is a shortcut for:
 *
 * ```
 * ddlog_config config = ddlog_default_config();
 * config.num_timely_workers = workers;
 * ddlog_run_with_config(&config, do_store, print_err_msg, init_state);
 * ```
 */
extern ddlog_prog ddlog_run(
        unsigned int workers,
        bool do_store,
        void (*print_err_msg)(const char *msg),
        ddlog_delta **init_state);

/*
 * Create an instance of DDlog program.
 *
 * `config` - DDlog engine configuration for the program.  Passing `NULL` in this
 * argument enables default configuration.
 *
 * `do_store` - set to true to store the copy of output tables inside DDlog.
 * When set, the client can use the following APIs to retrieve the contents of
 * tables:
 *	- `ddlog_dump_ovsdb_delta()`
 *	- `ddlog_dump_table()`
 * This has a cost in terms of memory and CPU.  In addition, the current implementation
 * serializes all writes to its internal copies of tables, introducing contention
 * when `workers > 1`.  Therefore, this flag should be set to `false` if the
 * client prefers to use DDlog in streaming mode, via the callback mechanism
 * (see below).
 *
 * `init_state` - when not NULL, DDlog will store a pointer to `ddlog_delta`
 * containing initial snapshot of output relations at this address.  The caller
 * is responsible for freeing this delta, e.g., using `ddlog_free_delta()`.
 *
 * `print_err_msg` - callback to redirect diagnostic messages to.  Before
 * returning an error, functions in this API invoke this callback to print
 * error explanation.
 *
 * Setting `print_err_msg` to NULL causes ddlog to print to `stderr`.
 *
 * Returns a program handle to be used in subsequent calls to
 * `ddlog_transaction_start()`,
 * `ddlog_transaction_commit()`, etc., or NULL in case of error.
 */
extern ddlog_prog ddlog_run_with_config(
        const ddlog_config *config,
        bool do_store,
        void (*print_err_msg)(const char *msg),
        ddlog_delta **init_state);

/*
 * Get DDlog table id by name.  The table name is a null-terminated UTF8
 * string.
 *
 * NOTE: for tables declared outside of the main DDlog module, fully qualified
 * module names must be used, e.g., the fully qualified name of a table named
 * "Pod" declared inside the "k8spolicy" module is "k8spolicy::Pod".
 *
 * On error, returns -1.
 */
extern table_id ddlog_get_table_id(ddlog_prog hprog, const char* tname);

/*
 * Get DDlog table name from id.
 *
 * Returns a null-terminated UTF8 string on success or NULL on error.
 */
extern const char* ddlog_get_table_name(ddlog_prog hprog, table_id id);

/*
 * Given a table name, returns the original name (from the 'original' DDlog
 * relation annotation), if present, or the table name itself otherwise.
 *
 * Returns a null-terminated UTF8 string on success or NULL on error.
 */
extern const char* ddlog_get_table_original_name(ddlog_prog hprog, const char* tname);

/*
 * Get DDlog index id by name.  The index name is a null-terminated UTF8
 * string.
 *
 * NOTE: for indexes declared outside of the main DDlog module, fully qualified
 * module names must be used.
 *
 * On error, returns -1.
 */
extern index_id ddlog_get_index_id(ddlog_prog hprog, const char* iname);

/*
 * Get DDlog index name from id.
 *
 * Returns a null-terminated UTF8 string on success or NULL on error.
 */
extern const char* ddlog_get_index_name(ddlog_prog hprog, index_id id);

/*
 * Record commands issued to DDlog via this API in a file.
 *
 * This is a debugging feature used to record DDlog commands issued through
 * functions in this file in a DDlog command file that can later be replayed
 * through the CLI interface.
 *
 * `fd` - file descriptor.  Passing -1 in this argument instructs DDlog to stop
 * recording. The caller is responsible for opening and closing the file.  They
 * can inject additional commands, e.g., `echo` to the file.
 *
 * IMPORTANT: this function is _not_ thread-safe and must not be invoked
 * concurrently with other functions in this API.
 */
extern int ddlog_record_commands(ddlog_prog hprog, int fd);

/*
 * Dump current snapshot of input tables to a file in a format suitable
 * for replay debugging.
 *
 * This function is intended to be used in conjunction with
 * `ddlog_record_commands`.  It is useful if one wants to start recording
 * after the program has been running for some time, or if the current log is
 * full and needs to be rotated out.  Simply calling `ddlog_record_commands`
 * with a new file descriptor at this point will generate an incomplete log that
 * will not reflect the state of input tables at the time recording is starting.
 * Such a log cannot be replayed in a meaningful way.
 *
 * Instead, we would like to start replay from the current input state (which
 * is in essence a compressed representation of the entire previous execution
 * history).  This can be achieved by first calling this function to store a
 * snapshot of input tables, followed by `ddlog_record_commands()` to continue
 * recording subsequent commands to the same file.
 *
 * This function generates input snapshot in the following format:
 *
 * ```
 * insert Table1[val1],
 * insert Table1[val2],
 * insert Table2[val3],
 * insert Table2[val4],
 * ...
 * ```
 *
 * NOTE: it does not wrap its output in a transaction.  The caller is
 * responsible for injecting `start;` and `commit;` commands, if necessary.
 *
 * `fd` - valid writable file descriptor.  The caller is responsible for opening
 * and closing the file.
 */
extern int ddlog_dump_input_snapshot(ddlog_prog hprog, int fd);

/*
 * Stops the program; deallocates all resources, invalidates the handle.
 *
 * All concurrent calls using the handle must complete before calling this
 * function.
 *
 * On success, returns `0`; on error, returns `-1` and prints error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 *
 * IMPORTANT: this function is _not_ thread-safe and must not be invoked
 * concurrently with other functions in this API.
 */
extern int ddlog_stop(ddlog_prog hprog);

/*
 * Start a transaction.
 *
 * On success, returns `0`; on error, returns `-1` and prints error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 *
 * This function will fail if another transaction is in progress.
 *
 * Within a transaction, updates to input relations are buffered until
 * `ddlog_transaction_commit()` is called.
 */
extern int ddlog_transaction_start(ddlog_prog hprog);

/*
 * Commit a transaction; propagate all buffered changes through all
 * rules in the program and update all output relations.
 *
 * On success, returns `0`; on error, returns `-1` and prints error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 *
 * This function will fail if there is no transaction in progress.
 */
extern int ddlog_transaction_commit(ddlog_prog hprog);

/*
 * Commit a transaction; propagate all buffered changes through all
 * rules in the program and update all output relations and returns
 * the set of changes.  On error, returns `NULL` and prints error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 *
 * This function will fail if there is no transaction in progress.
 */
extern ddlog_delta *
ddlog_transaction_commit_dump_changes(
        ddlog_prog hprog);

/*
 * Commit a transaction; propagate all buffered changes through all
 * rules in the program and update all output relations.  Once all
 * updates are finished, returns an array of changes to output relations.
 * Each record occurs in the array at most once.
 *
 * NOTE: The array returned by this function is owned by DDlog and must be
 * deallocated (along with all it contents) using the
 * `ddlog_free_record_updates()` function.
 *
 * On success, returns `0`; on error, returns `-1` and prints error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 *
 * This function will fail if there is no transaction in progress.
 */
extern int
ddlog_transaction_commit_dump_changes_as_array(
        ddlog_prog hprog,
        ddlog_record_update **changes,
        size_t *num_changes);

/*
 * Deallocate array of updates returned by
 * `ddlog_transaction_commit_dump_changes_as_array()`.  Both `changes` and
 * `num_changes` arguments must be equal to the values returned by a successful
 * call `ddlog_transaction_commit_dump_changes_as_array`.
 *
 * This function invalidates record handles stored in the `changes` array;
 * they must not be accessed after the call.
 */
extern void
ddlog_free_record_updates(
        ddlog_record_update *changes,
        size_t num_changes);

/*
 * Same as `ddlog_transaction_commit_dump_changes`, but serializes changes to a
 * FlatBuffer.  On success, returns pointer to FlatBuffer, size and capacity of
 * the buffer, and offset where valid data starts inside the buffer.
 */
extern int
ddlog_transaction_commit_dump_changes_to_flatbuf(
        ddlog_prog hprog,
        unsigned char** buf,
        size_t* buf_size,
        size_t* buf_capacity,
        size_t* buf_offset);

/*
 * Deallocate a FlatBuffer returned by
 * `ddlog_transaction_commit_dump_changes_to_flatbuf`.  Must be called once for
 * each buffer returned by a successful invocation of
 * `ddlog_transaction_commit_dump_changes_to_flatbuf`.
 */
extern void
ddlog_flatbuf_free(
        unsigned char* buf,
        size_t size,
        size_t capacity);

/*
 * Discard all buffered updates and abort the current transaction.
 *
 * On success, returns `0`; on error, returns `-1` and prints error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 *
 * This function will fail if there is no transaction in progress.
 */
extern int ddlog_transaction_rollback(ddlog_prog hprog);

/*
 * Apply updates to DDlog tables.  See the ddlog_cmd API below.
 *
 * On success, returns `0`. On error, returns a negative value and
 * writes error message (see `print_err_msg` parameter to `ddlog_run()`).
 *
 * Whether the function succeeds or fails, it consumes all commands in
 * the `upds` array (but not the array itself), so they can no longer be
 * accessed by the caller after the function returns.
 *
 * This function fails non-atomically: upon a failure, it may have applied
 * a _subset_ of input commands (one exception is when `ddlog_apply_updates`
 * is called with a single command, in which case it leaves the database
 * unmodified in case of a failure).  Use `ddlog_transaction_rollback()` to
 * bring the database back to a known state, specifically the state where it
 * was before the start of the transaction.
 */
extern int ddlog_apply_updates(ddlog_prog prog, ddlog_cmd **upds, size_t n);

/*
 * Apply updates, serialized into a FlatBuffer, to DDlog tables.
 *
 * The FlatBuffer schema is auto-generated from DDlog code and is stored in
 * `flatbuf/flatbuf.rs`.
 *
 * `buf` - pointer to FlatBuffer of size `n`.
 *
 * On success, returns `0`. On error, returns a negative value and
 * writes error message (see `print_err_msg` parameter to `ddlog_run()`).
 *
 */
extern int ddlog_apply_updates_from_flatbuf(ddlog_prog prog,
                                            const unsigned char *buf,
                                            size_t n);

/*
 * Query index by key.
 *
 * `idxid` - id of the index to dump.
 * `key` - query key.
 *     NOTE: the caller keeps ownership of `key` after the call and
 *     must deallocate it using `ddlog_free()`.
 * `cb` - callback invoked for each returned record.
 * `cb_arg` - opaque handle passed to each `cb invocation`.
 *
 * On success, returns `0`. On error, returns a negative value and
 * writes error message (see `print_err_msg` parameter to `ddlog_run()`).
 */
extern int
ddlog_query_index(ddlog_prog prog,
                  index_id idxid,
                  ddlog_record *key,
                  void (*cb)(uintptr_t arg, const ddlog_record *rec),
                  uintptr_t cb_arg);

/*
 * Perform a query serialized in a flatbuf; return result in another flatbuf.
 *
 * The FlatBuffer schema is auto-generated from DDlog code and is stored in
 * `flatbuf/flatbuf.rs`.
 *
 * `buf` - pointer to FlatBuffer of size `n` containing the query.
 *
 * The following values returned by the function describe the flatbuffer
 * containing response to the query.
 * `resbuf` - address where the function will write the pointer to the reply
 * flatbuf.
 * `resbuf_size` - address to store the size of the generated flatbuf.
 * `resbuf_capacity` - address to store generated flatbuf capacity.
 * `resbuf_offset` - address to store data offset inside the returned buffer.
 *
 * On success, returns `0`. On error, returns a negative value and
 * writes error message (see `print_err_msg` parameter to `ddlog_run()`).
 */
extern int ddlog_query_index_from_flatbuf(ddlog_prog prog,
                                          const unsigned char *buf,
                                          size_t n,
                                          unsigned char ** resbuf,
                                          size_t* resbuf_size,
                                          size_t* resbuf_capacity,
                                          size_t* resbuf_offset);

/*
 * Enumerates the entire contents of an index.
 * Note that an index over a multiset will still enumerate records only once.
 *
 * `idxid` - id of the index to dump.
 * `cb` - callback invoked for each record in the index.
 * `cb_arg` - opaque handle passed to each `cb invocation`.
 *
 * On success, returns `0`. On error, returns a negative value and
 * writes error message (see `print_err_msg` parameter to `ddlog_run()`).
 */
extern int
ddlog_dump_index(ddlog_prog prog,
                 index_id idxid,
                 void (*cb)(uintptr_t arg, const ddlog_record *rec),
                 uintptr_t cb_arg);


/*
 * Dump all values in an index to a flatbuf.
 *
 * The FlatBuffer schema is auto-generated from DDlog code and is stored in
 * `flatbuf/flatbuf.rs`.
 *
 * `idxid` - id of the index to dump.
 *
 * The following values returned by the function describe the flatbuffer
 * containing response to the query.
 * `resbuf` - address where the function will write the pointer to the reply
 * flatbuf.
 * `resbuf_size` - address to store the size of the generated flatbuf.
 * `resbuf_capacity` - address to store generated flatbuf capacity.
 * `resbuf_offset` - address to store data offset inside the returned buffer.
 *
 * On success, returns `0`. On error, returns a negative value and
 * writes error message (see `print_err_msg` parameter to `ddlog_run()`).
 *
 */
extern int ddlog_dump_index_to_flatbuf(ddlog_prog prog,
                                       index_id idxid,
                                       unsigned char ** resbuf,
                                       size_t* resbuf_size,
                                       size_t* resbuf_capacity,
                                       size_t* resbuf_offset);

/*
 * Remove all records from an input relation.
 *
 * Fails if there is no transaction in progress.
 *
 * On success, returns `0`. On error, returns a negative value and
 * writes error message (see `print_err_msg` parameter to `ddlog_run()`).
 */
extern int ddlog_clear_relation(ddlog_prog prog, table_id table);

/*
 * Dump the content of an output table by invoking `cb` for each value
 * in the table.
 *
 * `cb` returns `true` to allow enumeration to continue or `false` to
 * abort the dump.
 *
 * `cb_arg` is an opaque argument passed to each invocation.
 *
 * Requires that `hprog` was created by calling `ddlog_run()` with
 * `do_store` flag set to `true`.  Fails otherwise.
 *
 * The `rec` argument of the callback function is a borrowed reference
 * that is only valid for the duration of the callback.
 *
 * The content of the table returned by this function represents
 * database state after the last committed transaction.
 */
extern int ddlog_dump_table(ddlog_prog prog, table_id table,
                            bool (*cb)(uintptr_t arg, const ddlog_record *rec, ssize_t weight),
                            uintptr_t cb_arg);


/**********************************************************************
 * Delta API.
 **********************************************************************/

/*
 * NOTE: This API is _not_ thread-safe. It is the callers responsibility to ensure
 * that at most one thread can access `ddlog_delta` at the same time.
 */

/*
 * Creates an empty delta.
 */
extern ddlog_delta* ddlog_new_delta(void);

/*
 * Retrieve changes to a specific table.
 *
 * Returns a delta that only contains changes to one table.
 */
extern ddlog_delta* ddlog_delta_get_table(const ddlog_delta *delta, table_id table);

/*
 * Enumerate changes in this delta.
 *
 * Invokes `cb` for each record in `delta`.
 *
 * `cb_arg` is an opaque handle passed to each `cb invocation`.
 */
extern void ddlog_delta_enumerate(
    const ddlog_delta *delta,
    void (*cb)(uintptr_t arg,
               table_id table,
               const ddlog_record *rec,
               ssize_t weight),
    uintptr_t cb_arg);

/*
 * Remove changes to the specified table from `delta`.
 */
extern void ddlog_delta_clear_table(ddlog_delta *delta, table_id table);

/*
 * Remove changes to the specified table from `delta` and return them as a separate
 * delta.  The caller is responsible for deallocating the new delta.
 */
extern ddlog_delta* ddlog_delta_remove_table(ddlog_delta *delta, table_id table);

/*
 * Clear changes to all tables.  Leaves `delta` empty.
 */
extern void ddlog_delta_clear(ddlog_delta *delta);

/*
 * Adds the contents of `new_delta` to `delta`.
 *
 * Example 1: `delta` does not contain record `r`; `new_delta` contains record
 * `r` with weight `w`.  `r` gets added to `delta` with weight `w`.
 *
 * Example 2: `delta` contains record `r` with weight `w`; `new_delta` does
 * not contain `r`.  The state of `r` in `delta` does not change.
 *
 * Example 3: `delta` contains record `r` with weight `w`; `new_delta`
 * contains the same record with weight `-w`. The two changes
 * cancel out, and the record gets removed from `delta`.
 *
 * Example 4: `delta` and `new_delta` contain `r` with weights `w1` and `w2`.
 * While this operation is well-defined and will update the weight of `r` to
 * the sum of the two weights, it should only occur when working with multisets
 * or streams.
 */
extern void ddlog_delta_union(ddlog_delta *delta, const ddlog_delta *new_delta);

/*
 * Deallocate delta.  Invalidates the pointer.
 */
extern void ddlog_free_delta(ddlog_delta *delta);

/**********************************************************************
 * OVSDB API.
 **********************************************************************/

/*
 * Parse OVSDB JSON <table-updates> value into DDlog commands; apply
 * commands to a DDlog program.
 *
 * Must be called in the context of a transaction.
 *
 * `prefix` contains is the prefix to be added to JSON table names, e.g,
 * `OVN_Southbound.` or `OVN_Northbound.` for OVN southbound and
 * northbound database updates.
 *
 * `updates` is the JSON string, e.g.,
 * ```
 * {"Logical_Switch":{"ffe8d84e-b4a0-419e-b865-19f151eed878":{"new":{"acls":["set",[]],"dns_records":["set",[]],"external_ids":["map",[]],"load_balancer":["set",[]],"name":"lsw0","other_config":["map",[]],"ports":["set",[]],"qos_rules":["set",[]]}}}}
 * ```
 */
extern int ddlog_apply_ovsdb_updates(ddlog_prog hprog, const char *prefix,
                                     const char *updates);

/*
 * Dump Delta-Plus, Delta-Minus, and Delta-Update tables for OVSDB table
 * `table` declared in DDlog module `module`, as a sequence of OVSDB insert,
 * delete, and update commands in JSON format.
 *
 * `delta` - DDlog delta containing the latest snapshot of `table`.
 * `module` - a fully qualified name of a module.
 *
 * On success, returns `0` and stores a pointer to JSON string in
 * `json`.  This pointer must be later deallocated by calling
 * `ddlog_free_json()`
 *
 * On error, returns a negative number and writes error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 */
extern int ddlog_dump_ovsdb_delta_tables(
    ddlog_prog hprog,
    const ddlog_delta* delta,
    const char *module,
    const char *table, char **json);

/*
 * Dump output table for OVSDB table `table` declared in DDlog module
 * `module`, as a sequence of OVSDB insert and delete commands in JSON format.
 *
 * `delta` - DDlog delta containing the latest snapshot of `table`.
 * `module` - a fully qualified name of a module.
 *
 * On success, returns `0` and stores a pointer to JSON string in
 * `json`.  This pointer must be later deallocated by calling
 * `ddlog_free_json()`
 *
 * On error, returns a negative number and writes error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 */
extern int ddlog_dump_ovsdb_output_table(
    ddlog_prog hprog,
    const ddlog_delta* delta,
    const char *module,
    const char *table,
    char **json);

/*
 * Serializes table record 'rec' into a JSON object for an OVSDB insert,
 * update, or delete operation, respectively, to apply to OVSDB table 'table'.
 *
 * On success, returns `0` and stores a pointer to JSON string in
 * `json`.  This pointer must be later deallocated by calling
 * `ddlog_free_json()`
 *
 * On error, returns a negative number and writes error message
 * (see `print_err_msg` parameter to `ddlog_run()`).
 */
extern int ddlog_into_ovsdb_insert_str(
    ddlog_prog prog,
    const char *table,
    const ddlog_record *rec,
    char **json);
extern int ddlog_into_ovsdb_update_str(
    ddlog_prog prog,
    const char *table,
    const ddlog_record *rec,
    char **json);
extern int ddlog_into_ovsdb_delete_str(
    ddlog_prog prog,
    const char *table,
    const ddlog_record *rec,
    char **json);

/*
 * Deallocates strings returned by other functions in this API.
 */
extern void ddlog_free_json(char *json);

/***********************************************************************
 * Profiling
 ***********************************************************************/

/*
 * Controls recording of differential operator runtimes.  When enabled,
 * DDlog records each activation of every operator and prints the
 * per-operator CPU usage summary in the profile.  When disabled, the
 * recording stops, but the previously accumulated profile is preserved.
 *
 * Recording CPU events can be expensive in large dataflows and is
 * therefore disabled by default.
 */
extern int ddlog_enable_cpu_profiling(ddlog_prog prog, bool enable);

/*
 * Controls recording of the number of insertions and deletions per
 * arrangement.  Unlike the arrangement size profile, which tracks the
 * number of records in each arrangment, this feature tracks the amount
 * of churn.  For example adding one record and deleting one record will
 * show up as two changes in the change profile (but will cancel out in
 * the size profile).
 *
 * When change profiling is disabled, the recording stops, but the
 * previously accumulated profile is preserved.  By selectively enabling
 * change profiling for a subset of transactions, the user can focus
 * the analysis on specific parts of the program.
 */
extern int ddlog_enable_change_profiling(ddlog_prog prog, bool enable);

/*
 * Controls recording of timely operator runtimes. When enabled,
 * DDlog receives timely dataflow events and writes them out to a CSV file
 * where they can be queried later for useful information about program
 * execution. This is particularly useful for DDlog programs running with
 * multiple workers. When disabled, the recording stops, but the previously
 * accumulated profile is preserved.
 *
 * Recording timely events can be expensive in large dataflows and is
 * therefore disabled by default.
 */
extern int ddlog_enable_timely_profiling(ddlog_prog prog, bool enable);

/*
 * Dumps DDlog program runtime profile to a file.  `label` is an optional
 * label used, along with the current time, to generate a file name to
 * store the profile.  Set `label` to `NULL` to omit the label.
 *
 * Returns absolute path to the generated HTML profile file.  All profiles
 * for a process (even if the process runs multiple DDlog instances) will
 * be stored in the same directory.
 *
 * The returned string must be deallocated using `ddlog_string_free()`.
 */
extern char* ddlog_dump_profile(ddlog_prog prog, const char *label);

/*
 * Returns arrangement size profile as a JSON string.
 *
 * The returned string must be deallocated using `ddlog_string_free()`.
 *
 * Fails if the program runs with self-profiler disabled.
 * Returns `NULL` on error.
 */
extern char* ddlog_arrangement_size_profile(ddlog_prog prog);

/*
 * Returns peak arrangement size profile as a JSON string.
 *
 * The returned string must be deallocated using `ddlog_string_free()`.
 *
 * Fails if the program runs with self-profiler disabled.
 * Returns `NULL` on error.
 */
extern char* ddlog_peak_arrangement_size_profile(ddlog_prog prog);

/*
 * Returns arrangement change profile as a JSON string.  The
 * change profile can be empty if change profiling was never
 * enabled (using `ddlog_enable_change_profiling()`).  In this
 * case, the function returns an empty JSON array (`[]`).
 *
 * The returned string must be deallocated using `ddlog_string_free()`.
 *
 * Fails if the program runs with self-profiler disabled.
 * Returns `NULL` on error.
 */
extern char* ddlog_change_profile(ddlog_prog prog);

/*
 * Returns DDlog's CPU profile as a JSON string. The CPU
 * profile can be empty if CPU profiling was never
 * enabled (using `ddlog_enable_cpu_profiling()`).  In this
 * case, the function returns an empty JSON array (`[]`).
 *
 * The returned string must be deallocated using `ddlog_string_free()`.
 *
 * Fails if the program runs with self-profiler disabled.
 * On error, returns `NULL`.
 */
extern char* ddlog_cpu_profile(ddlog_prog prog);

/***********************************************************************
 * Record API
 ***********************************************************************/

/*
 * The record API is intended solely for passing values to and from
 * DDlog.  It allows constructing and reading values, but does not
 * provide methods to otherwise manipulate them, e.g., to lookup an
 * element in a set or map.
 *
 * This API supports the following ownership policy (see additional
 * details in individual function description):
 *
 * - The client obtains ownership of a record by creating it using
 *   `ddlog_bool()`, `ddlog_string()`, `ddlog_tuple()`, etc.  This is
 *   currently the only way to obtain ownership.
 *
 * - The client yields ownership by either passing the record to DDlog
 *   as part of an update command (see `ddlog_apply_updates()`) or by
 *   attaching it to another owned object (e.g., appending it to a
 *   vector using `ddlog_vector_push()`).
 *
 * - Each owned record encapsulates some dynamically allocated memory.
 *   To avoid memory leaks, the client must transfer the ownership of
 *   every record they own or deallocate the record using
 *   `ddlog_free()`.
 *
 * - There is a limited API for modifying _owned_ records, e.g., by
 *   appending elements to an array.
 *
 * - In addition to owned records, the client can also obtain pointers to
 *   *borrowed* records in one of two ways:
 *
 *    1. By invoking `ddlog_dump_table()` or `ddlog_dump_index()` to
 *       enumerate the content of an output table or index.  These functions
 *       take a user callback and invoke it once for each record in the table
 *       or index.  The record, passed as argument to the callback is owned
 *       by DDlog and is only valid for the duration of the callback.
 *       (TODO: the only reason for this is convenience, so the client
 *       does not need to worry about deallocating the record later.
 *       The API could be changed to return owned records.)
 *
 *    2. By querying another record of type tuple, vector, set or map.
 *       For instance, `ddlog_get_vector_elem()` returns a borrowed
 *       reference to an element of the vector.
 *
 * - The client may inspect a borrowed record, but not modify it, attach
 *   to another records or pass to DDlog as part of an update.
 *
 * - Owned records are represented in the API by mutable pointers
 *   (`ddlog_record*`), whereas borrowed records are represented by
 *   immutable pointers (`const ddlog_record*`).
 *
 * Type checking:
 *
 * The Record API does not perform any type checking, e.g., one can
 * create a vector with elements of different types or a struct whose
 * fields don't match declarations in the DDlog program. Type checking
 * is performed by DDlog before inserting records to the database, e.g.,
 * in the `ddlog_apply_updates()` function.  The function will fail if
 * it detects type mismatch between relation type and the record
 * supplied by the client.
 *
 * The client can rely on records read from the database to have types
 * that matches the corresponding DDlog output table declaration.
 */

/*
 * Dump record into a C string for debug printing.
 *
 * Returns `NULL` on error.
 *
 * The returned string must be deallocated using `ddlog_string_free()`.
 */
extern char* ddlog_dump_record(const ddlog_record *rec);

/*
 * Deallocate an owned record.
 */
extern void ddlog_free(ddlog_record *rec);

/*
 * Deallocate a C string returned by DDlog
 * (currently only applicable to the string returned by `ddlog_dump_profile()` and
 * `ddlog_dump_record()`).
 */
extern void ddlog_string_free(char *s);

/*
 * Create a Boolean value
 */
extern ddlog_record* ddlog_bool(bool b);

/*
 * Returns `true` if `rec` is a Boolean and `false` otherwise
 */
extern bool ddlog_is_bool(const ddlog_record *rec);

/*
 * Retrieves the value of a Boolean.
 *
 * Returns `false` if `rec` is not a Boolean.
 */
extern bool ddlog_get_bool(const ddlog_record *rec);

/*
 * Returns `true` if `rec` is an integer and `false` otherwise.
 */
extern bool ddlog_is_int(const ddlog_record *rec);

/*
 * Returns `true` if `rec` is a float and `false` otherwise.
 */
extern bool ddlog_is_float(const ddlog_record *rec);

/*
 * Returns `true` if `rec` is a double and `false` otherwise.
 */
extern bool ddlog_is_double(const ddlog_record *rec);

/*
 * Returns the fewest bits necessary to express the integer value,
 * not including the sign.
 *
 * Returns `0` if the `rec` is not an integer record.
 */
extern size_t ddlog_int_bits(const ddlog_record *rec);

/*
 * Create an integer value with arbitrary width and signedness.  Can be used to
 * populate any ddlog field of type `bit<N>`, `signed<N>` or `bigint`.
 *
 * `v` - byte array containing big endian two's complement representation of the number
 * `size` - size of `v` in bytes
 */
extern ddlog_record* ddlog_int(unsigned char * v, size_t size);

/*
 * Extract big-endian byte representation of arbitrary integer value.
 *
 * `buf`        - buffer to store the big-endian two's complement byte representation
 *                of the integer value
 * `capacity`   - buffer capacity
 *
 * Return value: if `capacity` is 0, returns the minimal buffer capacity necessary to
 * represent the value; otherwise returns the number of bytes stored in `buf` or `-1` if `buf`
 * is not big enough.
 */
extern ssize_t ddlog_get_int(
        const ddlog_record *rec,
        unsigned char *buf,
        size_t capacity);

/**
 * Create a copy of this DDlog record.  The user is responsible for deleting this record
 * using ddlog_free.
 */
extern ddlog_record* ddlog_clone(const ddlog_record* rec);

/*
 * Create an unsigned integer value.  Can be used to populate any ddlog field
 * of type `bit<N>`, `N<=64`
 */
extern ddlog_record* ddlog_u64(uint64_t v);

/*
 * Retrieves the value of an unsigned integer.
 *
 * Returns `0` if `rec` is not an integer or if its value does not
 * fit into 64 bits.
 */
extern uint64_t ddlog_get_u64(const ddlog_record *rec);

/*
 * Create a signed integer value.  Can be used to populate any ddlog field
 * of type `signed<N>`, `N<=64`
 */
extern ddlog_record* ddlog_i64(int64_t v);

/*
 * Retrieves the value of a signed integer.
 *
 * Returns `0` if `rec` is not an integer or if its value does not
 * fit into 64 bits.
 */
extern int64_t ddlog_get_i64(const ddlog_record *rec);

/*
 * Create a float value.
 */
extern ddlog_record* ddlog_float(float f);

/*
 * Retrieves the value of a float.
 *
 * Returns `0` if `rec` is not a float.
 */
extern float ddlog_get_float(const ddlog_record* rec);

/*
 * Create a double value.
 */
extern ddlog_record* ddlog_double(double d);

/*
 * Retrieves the value of a double.
 *
 * Returns `0` if `rec` is not a double.
 */
extern double ddlog_get_double(const ddlog_record* rec);

/*
 * Create an unsigned integer value.  Can be used to populate any ddlog field
 * of type `bit<N>`, `N<=128`
 */
extern ddlog_record* ddlog_u128(__uint128_t v);

/*
 * Retrieves the value of an integer.
 *
 * Returns `0` if `rec` is not an integer or if its value does not
 * fit into 128 bits.
 */
extern __uint128_t ddlog_get_u128(const ddlog_record *rec);

/*
 * Create a signed integer value.  Can be used to populate any ddlog field
 * of type `signed<N>`, `N<=128`
 */
extern ddlog_record* ddlog_i128(__int128_t v);

/*
 * Retrieves the value of a signed integer.
 *
 * Returns `0` if `rec` is not an integer or if its value does not
 * fit into 128 bits.
 */
extern __int128_t ddlog_get_i128(const ddlog_record *rec);

/*
 * Create a string value from a NULL-terminated string `s`.  This function
 * copies `s` to an internal buffer, so the caller is responsible for
 * deallocating `s` if it was dynamically allocated.
 *
 * Returns `NULL` if `s` is not a valid null-terminated UTF8 string.
 */
extern ddlog_record* ddlog_string(const char *s);

/*
 * Create a string value.
 *
 * `s` - points to the start of a UTF8 string.  The string does not have to be
 *       NULL-terminated.  The pointer must not be `NULL`, unless `len==0`, in
 *       which case the function ignores the value of the pointer and returns
 *       a record containing an empty string.
 * `len` - length of string in bytes.
 *
 * This function copies `s` to an internal buffer, so the caller is responsible for
 * deallocating `s` if it was dynamically allocated.
 *
 * Returns `NULL` if `s` is not a valid UTF8 string.
 */
extern ddlog_record* ddlog_string_with_length(const char * s, size_t len);

/*
 * Returns `true` if `rec` is a string and `false` otherwise
 */
extern bool ddlog_is_string(const ddlog_record *rec);

/*
 * Retrieves the length of a string in bytes.
 *
 * Returns `0` if `rec` is not a string.
 */
extern size_t ddlog_get_strlen(const ddlog_record *rec);

/*
 * Returns the contents of a DDlog string.
 *
 * On success, returns pointer to the string and stores the length of the
 * string in bytes in `len`.
 *
 * If `rec` is not a record of type string, returns `NULL`.
 *
 * IMPORTANT: The returned string is _not_ null-terminated.
 *
 * The pointer returned by this function points to an internal DDlog
 * buffer. The caller must not modify the contents of the string or
 * deallocate this pointer.  The lifetime of the pointer coincides with
 * the lifetime of the record it was obtained from, e.g., the pointer is
 * invalidated when the value is written to the database.
 */
extern const char * ddlog_get_str_with_length(const ddlog_record *rec, size_t *len);

/*
 * Create a serialized record from a NULL-terminated string `t` containing
 * the serialization scheme and a NULL-terminated string `s` containing
 * the serialized data.
 * This function copies `s` to an internal buffer, so the caller is
 * responsible for deallocating `s` if it was dynamically allocated.
 *
 * Returns `NULL` if `t` or `s` are not a valid null-terminated UTF8 strings.
 */
extern ddlog_record* ddlog_serialized(const char *t, const char *s);

/*
 * Create a serialized record.
 *
 * `t` - points to the start of a UTF8 string containing the
 *       serialization scheme. The string does not have to be
 *       NULL-terminated. The pointer must not be `NULL`
 * `t_len` - length of string in bytes.
 * `s` - points to the start of a UTF8 string containing the
 *       serialization scheme.  The string does not have to be
 *       NULL-terminated. The pointer must not be `NULL`, unless
 *       `s_len==0`, in which case the function ignores the value of the
 *       pointer and returns a record containing an empty string.
 * `s_len` - length of string in bytes.
 *
 * This function copies `s` to an internal buffer, so the caller is
 * responsible for deallocating `s` if it was dynamically
 * allocated. It assumes that `t` is a statically allocated string and
 * stores the pointer internally instead of copying it to another buffer.
 *
 * Returns `NULL` if `t` or `s` are not a valid UTF8 strings.
 */
extern ddlog_record* ddlog_serialized_with_length(const char *t, size_t t_len,
                                                  const char *s, size_t s_len);

/*
 * Returns `true` if `rec` is a serialized record and `false` otherwise
 */
extern bool ddlog_is_serialized(const ddlog_record *rec);

/*
 * Create a tuple with specified fields.
 *
 * `len` is the length of the `fields` array.
 * If `len` is greater than `0`, then `fields` must not be NULL.
 *
 * The function takes ownership of all records, invalidating all
 * `ddlog_record` pointers in `fields`.  However it does not take
 * ownership of the `fields` array itself.  The caller is responsible
 * for deallocating the array if needed.
 */
extern ddlog_record* ddlog_tuple(ddlog_record ** fields, size_t len);

/*
 * Returns `true` if `rec` is a tuple and false otherwise
 */
extern bool ddlog_is_tuple(const ddlog_record *rec);

/*
 * Retrieves the number of fields in a tuple.
 *
 * Returns `0` if `rec` is not a tuple.
 */
extern size_t ddlog_get_tuple_size(const ddlog_record *rec);

/*
 * Retrieves `i`th field of the tuple.
 *
 * Returns NULL if `tup` is not a tuple or if the tuple has fewer than `i`
 * fields.
 *
 * The pointer returned by this function is owned by DDlog. The caller
 * may inspect the returned record, but must not modify it, attach to
 * other records (e.g., using `ddlog_tuple_push()`) or write to the
 * database.  The lifetime of the pointer coincides with the lifetime of
 * the record it was obtained from, e.g., the pointer is invalidated
 * when the value is written to the database.
 */
extern const ddlog_record* ddlog_get_tuple_field(const ddlog_record* tup,
                                                 size_t i);

/*
 * Convenience method to create a 2-tuple.  Such tuples are useful,
 * e.g., in constructing maps out of key-value pairs.
 *
 * The function takes ownership of `v1` and `v2`.
 */
extern ddlog_record* ddlog_pair(ddlog_record *v1, ddlog_record *v2);

/*
 * An alternative way to construct tuples by adding fields one-by-one.
 *
 * To use this function, start with creating a tuple using, e.g.,
 *
 * ```
 * ddlog_tuple(NULL, 0);
 * ```
 *
 * and then call `ddlog_tuple_push()` once for each field, in the order
 * fields appear in the tuple.
 *
 * This function takes ownership of `rec`, which should not be used
 * after the call.
 */
extern void ddlog_tuple_push(ddlog_record *tup, ddlog_record *rec);

/*
 * Create a vector with specified elements.
 *
 * `len` is the length of the `recs` array.
 * If `len` is greater than `0`, then `recs` must not be NULL.
 *
 * The function takes ownership of all records, invalidating all
 * `ddlog_record` pointers in `recs`.  However it does not take ownership of
 * the `recs` array itself.  The caller is responsible for deallocating the
 * array if needed.
 */
extern ddlog_record* ddlog_vector(ddlog_record **recs, size_t len);

/*
 * Returns `true` if `rec` is a vector and false otherwise
 */
extern bool ddlog_is_vector(const ddlog_record *rec);

/*
 * Retrieves the number of elements in a vector.
 *
 * Returns `0` if `rec` is not a vector.
 */
extern size_t ddlog_get_vector_size(const ddlog_record *rec);

/*
 * Retrieves `i`th element of the vector.
 *
 * Returns NULL if `vec` is not a vector or if the vector is shorter than `i`.
 *
 * The pointer returned by this function is owned by DDlog. The caller
 * may inspect the returned record, but must not modify it, attach to
 * other records (e.g., using `ddlog_vector_push()`) or write to the
 * database.  The lifetime of the pointer coincides with the lifetime of
 * the record it was obtained from, e.g., the pointer is invalidated
 * when the value is written to the database.
 */
extern const ddlog_record* ddlog_get_vector_elem(const ddlog_record *vec,
                                                 size_t idx);

/*
 * Append a value at the end of the vector.
 *
 * This function takes ownership of `rec`, which should not be used after the
 * call.
 */
extern void ddlog_vector_push(ddlog_record *vec, ddlog_record *rec);

/*
 * Create a set with specified elements.
 *
 * `len` is the length of the `recs` array.  If `len` is greater than
 * `0`, then `recs` must not be NULL.
 *
 * The function takes ownership of all records, invalidating all
 * `ddlog_record` pointers in `recs`.  However it does not take
 * ownership of the `recs` array itself.  The caller is responsible for
 * deallocating the array if needed.
 */
extern ddlog_record* ddlog_set(ddlog_record **recs, size_t len);

/*
 * Returns `true` if `rec` is a set and false otherwise
 */
extern bool ddlog_is_set(const ddlog_record *rec);

/*
 * Retrieves the number of values in a set.
 *
 * Returns `0` if `rec` is not a set.
 */
extern size_t ddlog_get_set_size(const ddlog_record *rec);

/*
 * Retrieves `i`th element of the set.  The `ddlog_record` type
 * internally represents sets as vectors of elements.  `idx` indexes
 * into this vector.
 *
 * Returns NULL if `set` is not a set or if the set has fewer than `i`
 * elements.
 *
 * The pointer returned by this function is owned by DDlog. The caller
 * may inspect the returned record, but must not modify it, attach to
 * other records (e.g., using `ddlog_vector_push()`) or write to the
 * database.  The lifetime of the pointer coincides with the lifetime of
 * the record it was obtained from, e.g., the pointer is invalidated
 * when the value is written to the database.
 */
extern const ddlog_record* ddlog_get_set_elem(const ddlog_record* set,
                                              size_t i);

/*
 * Append a value to a set.  The `ddlog_record` type internally
 * represents sets as vectors of elements.  These vectors only get
 * converted to an actual set representation inside DDlog.  In
 * particular, duplicate values are not merged inside a record.
 *
 * This function takes ownership of `rec`, which should not be used
 * after the call.
 */
extern void ddlog_set_push(ddlog_record *set, ddlog_record *rec);

/*
 * Create a map with specified elements.  Each element in `recs` must be
 * a 2-tuple representing a key-value pair.
 *
 * `len` is the length of the `recs` array.  If `len` is greater than
 * `0`, then `recs` must not be NULL.
 *
 * The function takes ownership of all records, invalidating all
 * `ddlog_record` pointers in `recs`.  However it does not take
 * ownership of the `recs` array itself.  The caller is responsible for
 * deallocating the array if needed.
 */
extern ddlog_record* ddlog_map(ddlog_record **recs, size_t len);

/*
 * Returns `true` if `rec` is a map and false otherwise
 */
extern bool ddlog_is_map(const ddlog_record *rec);

/*
 * Retrieves the number of elements in a map.
 *
 * Returns `0` if `rec` is not a map.
 */
extern size_t ddlog_get_map_size(const ddlog_record *rec);

/*
 * Retrieves the key of the `i`th element of the map.  The `ddlog_record`
 * type internally represents maps as vectors of elements.  `i` indexes
 * into this vector.
 *
 * Returns NULL if `map` is not a map or if the map has fewer than `i`
 * elements.
 *
 * The pointer returned by this function is owned by DDlog. The caller
 * may inspect the returned record, but must not modify it, attach to
 * other records (e.g., using `ddlog_vector_push()`) or write to the
 * database.  The lifetime of the pointer coincides with the lifetime of
 * the record it was obtained from, e.g., the pointer is invalidated
 * when the value is written to the database.
 */
extern const ddlog_record* ddlog_get_map_key(const ddlog_record *map,
                                             size_t i);

/*
 * Retrieves the value of the `i`th element of the map.  The
 * `ddlog_record` type internally represents maps as vectors of
 * elements.  `i` indexes into this vector.
 *
 * Returns NULL if `map` is not a map or if the map has fewer than `i`
 * elements.
 *
 * The pointer returned by this function is owned by DDlog. The caller
 * may inspect the returned record, but must not modify it, attach to
 * other records (e.g., using `ddlog_vector_push()`) or write to the
 * database.  The lifetime of the pointer coincides with the lifetime of
 * the record it was obtained from, e.g., the pointer is invalidated
 * when the value is written to the database.
 */
extern const ddlog_record* ddlog_get_map_val(const ddlog_record *rec,
                                             size_t i);

/*
 * Append a key-value pair to a map.  The `ddlog_record` type internally
 * represents maps as vectors of elements.  These vectors only get
 * converted to an actual map representation inside DDlog.  In
 * particular, duplicate keys are not merged inside a record.
 *
 * This function takes ownership of `key` and `val`, which should not be
 * used after the call.
 */
extern void ddlog_map_push(ddlog_record *map,
                           ddlog_record *key, ddlog_record *val);

/*
 * Create a struct with specified constructor and arguments.  This creates a
 * "positional record" where arguments are identified by their order.
 *
 * The number and types of field should match the corresponding DDlog
 * constructor declaration.
 *
 * `constructor` can point to statically, dynamically or stack-allocated
 * strings.  The function copies constructor name to an internal buffer,
 * so the caller is responsible for deallocating it if necessary.
 *
 * `len` is the length of the `args` array.  If `len` is greater than
 * `0`, then `args` must not be NULL.
 *
 * The function takes ownership of all records, invalidating all
 * `ddlog_record` pointers in `args`.  However it does not take
 * ownership of the `args` array itself.  The caller is responsible for
 * deallocating the array if needed.
 */
extern ddlog_record* ddlog_struct(const char* constructor,
                                  ddlog_record ** args, size_t len);

/*
 * Create a struct with named fields with specified constructor and
 * fields.  This creates a "named struct" where arguments are
 * identified by their names.
 *
 * The number and types of field should match the corresponding DDlog
 * constructor declaration.
 *
 * `constructor` can point to statically, dynamically or stack-allocated
 * strings.  The function copies constructor name to an internal buffer,
 * so the caller is responsible for deallocating it if necessary.
 *
 * `len` is the length of the `args` array and the field_names array.
 * If `len` is greater than `0`, then `args` and `field_names` must
 * not be NULL.
 *
 * The function takes ownership of all records, invalidating all
 * `ddlog_record` pointers in `args`.  However it does not take
 * ownership of either `args`, `field_names`, or each field name.  The
 * caller is responsible for deallocating these arrays if needed.
 */
extern ddlog_record* ddlog_named_struct(const char* constructor,
                                        const char** field_names,
                                        ddlog_record** args,
                                        size_t len);

/*
 * Same as `ddlog_struct()`, but passes constructor name as
 * non-null-terminated string represented by its start address and length in
 * bytes.
 */
extern ddlog_record* ddlog_struct_with_length(const char* constructor,
                                              size_t constructor_len,
                                              ddlog_record ** args,
                                              size_t len);

/*
 * Same as `ddlog_struct()`, but assumes that `constructor` is a statically
 * allocated string and stores the pointer internally instead of copying it to
 * another buffer.
 */
extern ddlog_record* ddlog_struct_static_cons(const char *constructor,
                                              ddlog_record **args, size_t len);

/*
 * Same as ddlog_struct_static_cons(), but passes constructor name as
 * non-null-terminated string represented by its start address and length in
 * bytes.
 */
extern ddlog_record* ddlog_struct_static_cons_with_length(
        const char *constructor,
        size_t constructor_len,
        ddlog_record **args, size_t args_len);

/*
 * Returns `true` if `rec` is a struct.
 */
extern bool ddlog_is_struct(const ddlog_record *rec);

/*
 * Retrieves constructor name as a non-null-terminated string.  Returns
 * string length in bytes in `len`.
 *
 * Returns NULL if `rec` is not a struct.
 */
extern const char * ddlog_get_constructor_with_length(const ddlog_record *rec,
                                                      size_t *len);

/*
 * Retrieves `i`th argument of a struct.
 *
 * Returns NULL if `rec` is not a struct or if the struct has fewer than
 * `i` arguments.
 *
 * The pointer returned by this function is owned by DDlog. The caller
 * may inspect the returned record, but must not modify it, attach to
 * other records (e.g., using `ddlog_vector_push()`) or write to the
 * database.  The lifetime of the pointer coincides with the lifetime of
 * the record it was obtained from, e.g., the pointer is invalidated
 * when the value is written to the database.
 */
extern const ddlog_record* ddlog_get_struct_field(const ddlog_record* rec,
                                                  size_t i);

/*
 * Retrieves field 'name' of struct 'rec'.
 *
 * Returns NULL if `rec` is not a struct with named fields or if the struct
 * does not have a field with the given name.
 *
 * The pointer returned by this function is owned by DDlog. The caller
 * may inspect the returned record, but must not modify it, attach to
 * other records (e.g., using `ddlog_vector_push()`) or write to the
 * database.  The lifetime of the pointer coincides with the lifetime of
 * the record it was obtained from, e.g., the pointer is invalidated
 * when the value is written to the database.
 */
extern const ddlog_record* ddlog_get_named_struct_field(const ddlog_record* rec,
                                                        const char* name);

/**
 * Returns `true` if `rec` is a struct with named fields.
 */
extern bool ddlog_is_named_struct(const ddlog_record* rec);

/*
 * Retrieves the name of the i-th field of the record.
 *
 * Returns NULL if `rec` is not a struct with named fields or if the struct
 * does not have a field with the given name, or the field index is
 * out of bounds.
 *
 * The pointer returned by this function is owned by DDlog. The caller
 * may inspect the returned record, but must not modify it, attach to
 * other records (e.g., using `ddlog_vector_push()`) or write to the
 * database.  The lifetime of the pointer coincides with the lifetime of
 * the record it was obtained from, e.g., the pointer is invalidated
 * when the value is written to the database.
 */
extern const char* ddlog_get_named_struct_field_name(const ddlog_record* rec,
                                                     size_t i,
                                                     size_t *len);

/***********************************************************************
 * Command API
 ***********************************************************************/

/*
 * Create an insert command.
 *
 * `table` - input table to insert to.
 * `rec` - record to insert.  The function takes ownership of this record.
 *
 * Returns pointer to a new command, which can be sent to DDlog by calling
 * `ddlog_apply_updates()`.
 *
 * This function never fails; however the command it creates may fail to
 * execute, causing `ddlog_apply_updates()` to return an error if:
 * - `table` is not a valid input table id
 * - `rec` does not match the record type of `table`
 * - The table has a primary key and there exists a record with the same key
 *   as `rec` in the table.
 */
extern ddlog_cmd* ddlog_insert_cmd(table_id table, ddlog_record *rec);

/*
 * Create an insert-or-update command that inserts a new record, deleting
 * an existing record with the same primary key, if there is one.
 *
 * `table` - input table to insert to.
 * `rec` - record to insert.  The function takes ownership of this record.
 *
 * Returns pointer to a new command, which can be sent to DDlog by calling
 * `ddlog_apply_updates()`.
 *
 * This function never fails; however the command it creates may fail to
 * execute, causing `ddlog_apply_updates()` to return an error if:
 * - `table` is not a valid input table id
 * - `table` does not have a primary key
 * - `rec` does not match the record type of `table`
 */
extern ddlog_cmd* ddlog_insert_or_update_cmd(table_id table, ddlog_record *rec);

/*
 * Create a delete-by-value command.
 *
 * `table` - input table to delete from.
 * `rec` - record to delete.  The function takes ownership of this record.
 *
 * Returns pointer to a new command, which can be sent to DDlog by calling
 * `ddlog_apply_updates()`.
 *
 * This function never fails; however the command it creates may fail to
 * execute, causing `ddlog_apply_updates()` to return an error if:
 * - `table` is not a valid input table id
 * - `rec` does not match the record type of `table`
 * - `table` has a primary key and record `rec` does not exist in `table`
 *   (NOTE: for tables without a primary key, the command succeeds even if
 *   the record does not exist, in which case it is a no-op)
 */
extern ddlog_cmd* ddlog_delete_val_cmd(table_id table, ddlog_record *rec);

/*
 * Create a delete-by-key command.
 *
 * `table` - input table to delete from.
 * `rec` - key to delete.  The function takes ownership of this record.
 *
 * Returns pointer to a new command, which can be sent to DDlog by calling
 * `ddlog_apply_updates()`.
 *
 * This function never fails; however the command it creates may fail to
 * execute, causing `ddlog_apply_updates()` to return an error if:
 * - `table` is not a valid input table id
 * - `table` does not have a primary key
 * - `rec` does not match the primary key type of `table`
 * - a record with the specified key does not exist in `table`
 */
extern ddlog_cmd* ddlog_delete_key_cmd(table_id table, ddlog_record *rec);

/*
 * Create a modify-by-key command.
 *
 * `table` - input table to delete from.
 * `key` - key to modify.  The function takes ownership of this record.
 * `values` - values to modify.  The function takes ownership of this record.
 *
 * Returns pointer to a new command, which can be sent to DDlog by calling
 * `ddlog_apply_updates()`.
 *
 * This function never fails; however the command it creates may fail to
 * execute, causing `ddlog_apply_updates()` to return an error if:
 * - `table` is not a valid input table id
 * - `table` does not have a primary key
 * - `key` does not match the primary key type of `table`
 * - a record with the specified key does not exist in `table`
 * - `values` does not match the columns of `table`
 */
extern ddlog_cmd* ddlog_modify_cmd(table_id table, ddlog_record* key, ddlog_record* values);

#endif
